package quirk;

import java.io.IOException;
import java.io.StringReader;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Communicates with the Index to get the necessary
 * data for each QUIRK command
 * 
 * @author Andrew Bernard
 */
public class IndexManager {   
  private Index index = null;
  
  /**
   * Constructor. Creates a new instance of Index.
   */
  public IndexManager() {
    index = new Index();
  }
  
  public String getCorpusDetails() {
    StringBuffer stats = new StringBuffer();
    stats.append("\n-------- Corpus Files --------");
    
    List dataHeaderList = index.getDataHeaderList();
    Iterator itr = dataHeaderList.iterator();
    while(itr.hasNext()) {
      Index.DataHeader dataHeader = (Index.DataHeader)itr.next();
      stats.append("\n\nFile name: "+dataHeader.fileName)
           .append("\nCharacters: "+dataHeader.charsInFile)
           .append("\nWords: "+dataHeader.wordsInFile)
           .append("\nLines: "+dataHeader.linesInFile);
    }
    
    Index.Summary summary = index.getSummary();
    stats.append("\n\n-------- Corpus Summary --------")
         .append("\nCharacters: " + summary.charsInCorpus)
         .append("\nWords: " + summary.wordsInCorpus)
         .append("\nLines: " + summary.linesInCorpus)
         .append("\nFiles: " + summary.filesInCorpus);
    
    return stats.toString();
  }
  
  public String getIndexDetails() {
    StringBuffer details = new StringBuffer();
    Index.Header header = index.getHeader();
    details.append("\n-------- Index Summary --------")
           .append("\nPath: "+header.indexPath)
           .append("\nCreation Date: "+header.creationDate)
           .append("\nStop File Path: "+header.stopFilePath)
           .append("\nCustom break chars: "+header.breakChars)
           .append("\nIndex Size: ")
           .append(NumberFormat.getIntegerInstance().format(header.indexFileSize));
    
    return details.toString();
  }
  
  public List runQuery(String queryString) 
  throws IOException, UserInteractionException {
    Query query = new Query(queryString);
    List wordsInQuery = getQueryWords(queryString.toLowerCase());
    List dataHeaderList = index.getDataHeaderList();
    return applyQueryPattern(query, wordsInQuery, dataHeaderList);
  }
  
  /**
   * This method does the following:
   *    1. Create a QueryWord object for each word in the query
   *    2. For each line in the index, set the word object to true or false if it exists in the line
   *    3. Apply the query pattern to each set of word objects for each line
   *    4. Store the results if the query returns a match
   * @param q the Query object used to evaluate the query
   * @param wordsInQuery a List of words (as String objects) in the query
   * @param dataHeaderList a List of the data headers (Index.DataHeader) in the index
   * @return a List of Query.Result objects
   */
  private List applyQueryPattern(Query query, List wordsInQuery, List dataHeaderList) {
    List results = new ArrayList();
    Iterator itrData = dataHeaderList.iterator();
    while(itrData.hasNext()) {
      Index.DataHeader dataHeader = (Index.DataHeader)itrData.next();
      dataHeader.isOutdated = dataHeader.creationDate > index.getCreationDate();
      index.setPosition(dataHeader.dataStartPos);
      
      for(int lineNum = 0; lineNum < dataHeader.linesInFile; lineNum++) {
        int filter = (int)index.getValue(dataHeader.lineFilterLen);
        boolean queryMatch = applyQueryToLine(query, wordsInQuery, filter, lineNum);
        if(queryMatch) {
          Query.Result r = query.new Result(dataHeader, lineNum, filter);
          results.add(r);
        }
      }      
    }
    return results;
  }
  
  private boolean applyQueryToLine(Query query, List wordsInQuery, int lineFilter, int lineNum) {
    QueryWord[] lineWordMap = new QueryWord[wordsInQuery.size()];
    
    for(int w = 0; w < wordsInQuery.size(); w++) {
      String word = wordsInQuery.get(w).toString().toLowerCase();
      boolean foundPossibleMatch = index.isWordOnLine(lineFilter, lineNum, word);          
      lineWordMap[w] = new QueryWord(word, foundPossibleMatch);
    }    
    return query.evaluate(lineWordMap);
  }
    
  /**
   * Returns a list of the words in the query as string objects.
   */
  private List getQueryWords(String query) 
  throws IOException, UserInteractionException {
    ArrayList words = new ArrayList();
    StringReader reader = new StringReader(query);
    String wordText = "";
    
    for(int ch = reader.read(); ch != Constants.EOF; ch = reader.read()) {
      if(Query.isGrammar(ch))
        if(wordText.length() > 0) {
          words.add(wordText);
          wordText = new String();
        }
      else if(index.isBreakCharacter(ch))
        throw new UserInteractionException(Constants.ERR_CHARS);
      else
        wordText += (char)ch;
    }
    return words;
  }
  
  public void refineQuery(String refinement, List queryResults) 
  throws IOException, UserInteractionException {
    Query refinedQuery = new Query(refinement);
    List wordsInQuery = getQueryWords(refinement);
    Iterator itrResults = queryResults.iterator();
    while(itrResults.hasNext()) {
      Query.Result result = (Query.Result)itrResults.next();
      boolean queryMatch = applyQueryToLine(refinedQuery, wordsInQuery, result.lineFilter, result.lineNum);
      if(!queryMatch)
        itrResults.remove();
    }
  }
}
